// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
use tauri::{Manager, Window, ResourceId, ResourceTable, Emitter};
use futures_util::TryStreamExt;
use tokio::sync::{oneshot::{channel, Receiver, Sender}, Mutex};

struct AbortSender(Sender<()>);
impl tauri::Resource for AbortSender {}

impl AbortSender {
    fn abort(self) {
        let _ = self.0.send(());
    }
}

struct AbortReceiver(Receiver<()>);
impl tauri::Resource for AbortReceiver {}

struct FetchRequest {
    fut: Arc<Pin<Box<dyn Future<Output = Result<reqwest::Response, String>> + Send + Sync>>>,
    abort_tx_rid: ResourceId,
    abort_rx_rid: ResourceId,
}
impl tauri::Resource for FetchRequest {}

trait AddRequest {
    fn add_request(&mut self, fut: Pin<Box<dyn Future<Output = Result<reqwest::Response, String>> + Send + Sync>>) -> ResourceId;
}

impl AddRequest for ResourceTable {
    fn add_request(&mut self, fut: Pin<Box<dyn Future<Output = Result<reqwest::Response, String>> + Send + Sync>>) -> ResourceId {
        let (tx, rx) = channel::<()>();
        let (tx, rx) = (AbortSender(tx), AbortReceiver(rx));
        let req = FetchRequest {
            fut: Arc::new(fut),
            abort_tx_rid: self.add(tx),
            abort_rx_rid: self.add(rx),
        };
        self.add(req)
    }
}

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[tauri::command]
async fn request(
    window: Window,
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
    timeout: Option<u64>,
    max_redirects: Option<usize>,
) -> Result<String, String> {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(timeout.unwrap_or(30)))
        .redirect(reqwest::redirect::Policy::limited(max_redirects.unwrap_or(10)))
        .build()
        .map_err(|e| e.to_string())?;

    let mut request = client
        .request(
            method.parse::<reqwest::Method>().map_err(|e| e.to_string())?,
            &url,
        );

    // 添加请求头
    for (key, value) in headers {
        request = request.header(key, value);
    }

    // 添加请求体
    if let Some(body_content) = body {
        request = request.body(body_content);
    }

    let fut = async move { request.send().await.map_err(|e| e.to_string()) };
    let mut resources_table = window.resources_table();
    let rid = resources_table.add_request(Box::pin(fut));

    let (req, abort_rx) = {
        let mut resources_table = window.resources_table();
        let req = resources_table.get::<FetchRequest>(rid).map_err(|e| e.to_string())?;
        let abort_rx = resources_table.take::<AbortReceiver>(req.abort_rx_rid).map_err(|e| e.to_string())?;
        (req, abort_rx)
    };

    let Some(abort_rx) = Arc::into_inner(abort_rx) else {
        return Err("Request canceled".to_string());
    };

    let response = tokio::select! {
        res = req.fut.as_ref() => res.await?,
        _ = abort_rx.0 => {
            let mut resources_table = window.resources_table();
            resources_table.close(rid).map_err(|e| e.to_string())?;
            return Err("Request canceled".to_string());
        }
    };

    // 检查响应头中的 Content-Type
    let content_type = response
        .headers()
        .get("content-type")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    // 如果是流式响应，使用 emit 推送数据
    if content_type.contains("text/event-stream") || content_type.contains("application/x-ndjson") {
        let mut stream = response.bytes_stream();
        while let Ok(Some(bytes)) = stream.try_next().await {
            if let Ok(text) = String::from_utf8(bytes.to_vec()) {
                window.emit("stream-data", text).map_err(|e| e.to_string())?;
            }
        }
        window.emit("stream-end", ()).map_err(|e| e.to_string())?;
        return Ok("Stream completed".to_string());
    }

    // 对于普通响应，返回完整内容
    let response_text = response
        .text()
        .await
        .map_err(|e| e.to_string())?;

    Ok(response_text)
}

#[tauri::command]
fn request_cancel(window: Window, rid: ResourceId) -> Result<(), String> {
    let mut resources_table = window.resources_table();
    let req = resources_table.get::<FetchRequest>(rid).map_err(|e| e.to_string())?;
    let abort_tx = resources_table.take::<AbortSender>(req.abort_tx_rid).map_err(|e| e.to_string())?;
    if let Some(abort_tx) = Arc::into_inner(abort_tx) {
        abort_tx.abort();
    }
    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet, request, request_cancel])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
